#include "drawArea.h"

drawArea::drawArea(QWidget *parent, gObjectContainer *cont): 
    QWidget(parent), previousObject(0), container(cont), buffContainer(cont),
	control(cont){
	
	once = false;
	distance = false;
	numerate = false;
	changed = false;
	listTip = new listToolTipDialog(this);
	setMouseTracking(true);
	control.setMatrix(&transform);
	updateContainer();
	
}

QMatrix drawArea::getMatrix() const{

	return transform;

}

void drawArea::setMatrix(QMatrix &matrix){

	transform = matrix;
	update();

}

void drawArea::setBackground(QPainter &painter){

	QBrush brush;
	
	brush.setStyle(Qt::SolidPattern);
	brush.setColor(CANVAS_BACKGROUND);
	painter.fillRect(0, 0, width(), height(), brush);

}

void drawArea::setFirstTransform(){

	if(!once){
		transform.translate(-1, height());
		transform.scale(1, -1);	
		once = true;
	}

}

void drawArea::drawAxis(QPainter &painter){

	QPen pen;
	QPoint point(0, 0);
	
	point = transform.map(point);
	pen.setStyle(AXIS_STYLE);
	pen.setColor(QColor(AXIS_COLOR));
	painter.setPen(pen);
	//std::numeric_limits<int>::min()
	//std::numeric_limits<int>::max()
	painter.drawLine(point.x(), 0, point.x(), height());
	painter.drawLine(0, point.y(), width(), point.y());

}

void drawArea::drawBuff(QPainter &painter){

	setStyles(painter,
			Qt::NoBrush,
			POINT_BRUSH_MOUSEOVER_COLOR,
			POINT_PEN_MOUSEOVER_STYLE,
			Qt::darkGreen);
	painter.drawPath(buffCuts);

}

void drawArea::drawArrow(QPainter &painter){

	QPainterPath path;
	
	//draw text labels
	setStyles(painter,
		Qt::NoBrush,
		MOVE_COLOR,
		LINE_STYLE,
		LINE_COLOR);
	painter.drawText(7, height() - 55, tr("y"));
	painter.drawText(55, height() - 4, tr("x"));
	//transform coordinates
	painter.save();
	painter.translate(10, height() - 8);
	painter.scale(1, -1);
	//draw y's arrow
	path.moveTo(0, 40);
	path.lineTo(-5, 30);
	path.lineTo(5, 30);
	path.lineTo(0, 40);
	setStyles(painter,
		Qt::SolidPattern,
		MOVE_COLOR,
		LINE_STYLE,
		LINE_COLOR);
	painter.drawPath(path);
	//draw line
	path = QPainterPath();
	path.moveTo(0, 30);
	path.lineTo(0, 0);
	path.lineTo(30, 0);
	setStyles(painter,
		Qt::NoBrush,
		MOVE_COLOR,
		LINE_STYLE,
		LINE_COLOR);
	painter.drawPath(path);
	//draw x's arrow
	path = QPainterPath();
	path.moveTo(40, 0);
	path.lineTo(30, -5);
	path.lineTo(30, 5);
	path.lineTo(40, 0);
	setStyles(painter,
		Qt::SolidPattern,
		POINT_PEN_COLOR,
		LINE_STYLE,
		LINE_COLOR);
	painter.drawPath(path);
	painter.restore();
	
}

float drawArea::vectorAngle(float a0, float a1, float b0, float b1){

	return atan2(b1, b0) - atan2(a1, a0);

}

void drawArea::keyPressEvent(QKeyEvent *event){

	if(event->key() == Qt::Key_Up)	
		transform.translate(0, 5);
	else if(event->key() == Qt::Key_Down)
		transform.translate(0, -5);
	else if(event->key() == Qt::Key_Left)
		transform.translate(-5, 0);
	else if(event->key() == Qt::Key_Right)
		transform.translate(5, 0);
	else if(event->key() == Qt::Key_PageUp)
		transform.scale(1.1 , 1.1);
	else if(event->key() == Qt::Key_PageDown)
		transform.scale(0.9 , 0.9);
	update();

}

void drawArea::setStyles(QPainter &painter, 
	Qt::BrushStyle brushStyle, 
	Qt::GlobalColor brushColor, 
	Qt::PenStyle penStyle,
	Qt::GlobalColor penColor){
		
	QPen pen;
	QBrush brush;
		
	brush.setStyle(brushStyle);
	brush.setColor(brushColor);
	pen.setStyle(penStyle);
	pen.setColor(penColor);
	painter.setPen(pen);
	painter.setBrush(brush);
		
}

void drawArea::drawCircleAxis(QPainter &painter){

	gPathLine line;
	
	if(control.isMiddle()){
		setStyles(painter,
			Qt::NoBrush,
			POINT_BRUSH_MOUSEOVER_COLOR,
			MIDDLE_AXIS_STYLE,
			AXIS_COLOR);
			line.addAxis(*control.getClickedGObject(), *control.getClickedGTypeGo());
		painter.drawPath(line);
	}

}

void drawArea::drawNumbers(QPainter &painter){
	
	if(numerate){
		setStyles(painter,
			POINT_BRUSH_MOUSEOVER_STYLE,
			POINT_BRUSH_MOUSEOVER_COLOR,
			POINT_PEN_MOUSEOVER_STYLE,
			POINT_PEN_MOUSEOVER_COLOR);
			
			gObjectContainer::iterator iter = container->begin();
			int i = 1;
			QPointF point;
			QString str;
			QFont font;
			
			font.setPointSize(12);
			painter.setFont(font);
			
			painter.resetMatrix();
			for(; iter != container->end(); iter++){
				gObject &object = **iter;
				gTypeGo &go0 = *dynamic_cast<gTypeGo *>(object[0]);
				gTypeGo &go1 = *dynamic_cast<gTypeGo *>(object[object.size() - 1]);
				point.setX(go0[0]);
				point.setY(go0[1]);
				point = transform.map(point);
				point.setX(point.x() + POINT_DISTANCE_X);
				point.setY(point.y() + POINT_DISTANCE_Y);
				painter.drawText(point, str.setNum(i));
				point.setX(go1[0]);
				point.setY(go1[1]);
				point = transform.map(point);
				point.setX(point.x() + POINT_DISTANCE_X);
				point.setY(point.y() + POINT_DISTANCE_Y);
				painter.drawText(point, str.setNum(i + 1));
				i++;
			}
			painter.setWorldMatrix(transform);
		
	}

}

void drawArea::highligth(QPainter &painter, 
	gObject &object, 
	gTypeGo &go, 
	const QPointF &p){

    int i = object.getIndexByObject(&go);
    int j = container->getIndexByObject(&object);
	gPathPoints point(&transform);
	gPathLine line;
    gTypeGo &buffGo = dynamic_cast<gTypeGo &>(*buffContainer[j]->at(i));
    QPointF buffPoint(buffGo[0], buffGo[1]);
	
    line.addLineGo(object, i);
    point.addGBuffPoint(buffPoint);
    point.addGPoint(p);
	setStyles(painter, 
		Qt::NoBrush, 
		POINT_BRUSH_MOUSEOVER_COLOR, 
		POINT_PEN_MOUSEOVER_STYLE,
		POINT_PEN_MOUSEOVER_COLOR);
	painter.drawPath(line);
	setStyles(painter, 
		POINT_BRUSH_MOUSEOVER_STYLE, 
		POINT_BRUSH_MOUSEOVER_COLOR, 
		POINT_PEN_MOUSEOVER_STYLE,
		POINT_PEN_MOUSEOVER_COLOR);
	painter.drawPath(point);

}

void drawArea::pointOnMouseOver(QPainter &painter){
	
	if(control.isActive()){
		setCursor(Qt::PointingHandCursor);
		highligth(painter, 
			*control.getActiveGObject(),
			*control.getActiveGTypeGo(),
			control.getActivePoint());
	}
	else
		setCursor(Qt::ArrowCursor);	
		
}

void drawArea::pointOnClicked(QPainter &painter){
	
	
	if(control.isClicked())
		highligth(painter, 
			*control.getClickedGObject(),
			*control.getClickedGTypeGo(),
			control.getClickedPoint());
	
}

void drawArea::pointDoubleClicked(QPainter &painter){

	gPathPoints points(&transform);
	gPathCuts cuts;

	if(control.isDoubleClicked()){
		points.addGObject(*control.getDoubleClickedGObject());
		cuts.addGObject(*control.getDoubleClickedGObject());
		setStyles(painter, 
			Qt::NoBrush, 
			POINT_BRUSH_MOUSEOVER_COLOR, 
			POINT_PEN_MOUSEOVER_STYLE,
			POINT_PEN_DOUBLECLICK_COLOR);
		painter.drawPath(points);
		setStyles(painter, 
			Qt::NoBrush, 
			POINT_BRUSH_MOUSEOVER_COLOR, 
			POINT_PEN_MOUSEOVER_STYLE,
			LINE_PEN_DOUBLECLICK_COLOR);
		painter.drawPath(cuts);
	}

}

bool drawArea::event(QEvent *event){
	
	if(event->type() == QEvent::ToolTip) {
		QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
		if(control.isActive()){
			if(distance){
				QPointF zPoint;
				gTypeGo *clickedGo, *activeGo;
				
				clickedGo = control.getClickedGTypeGo();
				activeGo = control.getActiveGTypeGo();
				zPoint.setX((*clickedGo)[2]);
				zPoint.setY((*activeGo)[2]);
				pointToolTip::showDistance(
					helpEvent->globalPos(),
					control.getClickedPoint(),
					control.getActivePoint(),
					zPoint,
					this);
			}
			else{
				gObjectContainer high(container->getHight(*control.getActiveGTypeGo()));
				
				pointToolTip::showGInformation(
					helpEvent->globalPos(), 
					control.getActiveGObject(),
					control.getActiveGTypeGo(),
					this
					);
			}
		}
		else{
			pointToolTip::hideText();
			event->ignore();
		}
		return true;
	}
	return QWidget::event(event);
	
}

void drawArea::wheelEvent(QWheelEvent *event){
	
	QMatrix matrix(transform.inverted());
	const QPoint &point = matrix.map(event->pos());
	float sc = 0.9;
	
	if(event->delta() > 0)
		sc = 1.1;
	transform.scale(sc , sc);
	transform.translate(-(point.x() * sc - point.x()), -(point.y() * sc - point.y()));
	event->accept();
	emit scaleChanged();
	update();
	
}

void drawArea::mouseMoveEvent(QMouseEvent *event){

	QPointF point(event->posF());
	QMatrix invert(transform.inverted());
	QPointF realPoint(invert.map(point));
	int sizeX, sizeY;
	
	if(control.intersect(point))
		realPoint = control.getActivePoint();
	emit realPosF(&realPoint);
	if(control.isPressed()){
		sizeX = point.x() - buffPoint.x();
		sizeY = buffPoint.y() - point.y();	
		transform.translate(sizeX / transform.m11(), sizeY / transform.m11());
		buffPoint = point; 
	}
	update();

}

void drawArea::mouseDoubleClickEvent(QMouseEvent *event){

	QPointF point(event->posF());
	
	if(event->button() == Qt::LeftButton){
		control.doubleClicked(point);
		if(control.isDoubleClicked())
			emit gObjectDoubleClicked(control.getDoubleClickedGObject());
		else
			emit gObjectDoubleClicked(0);
	}
	update();

}

void drawArea::mousePressEvent(QMouseEvent *event){
	
	QPointF point(event->posF());

	if(event->button() == Qt::LeftButton)
		control.pressed(point);
	buffPoint = point;
	update();

}

void drawArea::showEvent(QShowEvent *event){
	
	setFirstTransform();
	
}

void drawArea::mouseReleaseEvent(QMouseEvent *event){

	QPointF point(event->posF());
	
	if(event->button() == Qt::LeftButton){
		control.clicked(point);
		if(!control.isActive())
			distance = false;
		if(control.isWasClicked()){
			if(control.isClicked()){
				if(control.getDoubleClickedGObject()){
					gObject points(control.getDoubleClickedGObject()->getHight(*control.getClickedGTypeGo()));
				
					listTip->setObject(container, &points, control.getDoubleClickedGObject());
				}
				else{
					gObjectContainer points(container->getHight(*control.getClickedGTypeGo()));
					
					listTip->setContainer(&points);
				}
				if(listTip->count() > 1){
					listTip->move(event->globalPos());
					listTip->resize(240, 130);
					listTip->exec();
					
					if(listTip->getState() != listToolTipDialog::NONE){
						gObject *object = container->at(listTip->getGObject());
						
						control.clicked(object, listTip->getGTypeGo());
						if(listTip->getState() == listToolTipDialog::DOUBLECLICKED){
							emit gObjectDoubleClicked(object);
							control.doubleClicked(object);
						}
						emit gTypeGoClicked(listTip->getGTypeGo(), control.getClickedPoint());
					}
					else
						control.clicked(NULL, NULL);
				}
				else
					emit gTypeGoClicked(control.getClickedGTypeGo(), control.getClickedPoint());
			}
			else
				emit gTypeGoClicked(NULL, control.getClickedPoint());
		}
	}
	update();

}

void drawArea::contextMenuEvent(QContextMenuEvent *event){

	drawMenu menu(this);
	
	menu.setDistance(distance);
	menu.setNumerate(numerate);
	menu.exec(event->globalPos());

}

void drawArea::updateBuffContainer(){

	gPathBuff buffBuffCuts;

	buffBuffCuts.addGContainer(buffContainer);
	buffCuts = buffBuffCuts;

}

void drawArea::updateContainer(){

	gPathCuts cutsBuff;
	gPathMoves movesBuff;
	gPathPoints buffPoints(&transform);
	
	cutsBuff.addGContainer(*container);
	movesBuff.addGContainer(*container);
	buffPoints.addGContainer(*container);
	cuts = cutsBuff;
	moves = movesBuff;

}

void drawArea::paintEvent(QPaintEvent *event){

	QPainter painter(this);
	QPen pen;
	QBrush brush;

	//painter.setRenderHints(QPainter::Antialiasing, true);
	setBackground(painter);
	drawArrow(painter);
	drawAxis(painter);
    drawHighlight(painter);
	painter.setMatrix(transform, true);
	drawBuff(painter);
	drawCuts(painter);
	drawMoves(painter);
	drawCircleAxis(painter);
	drawPoints(painter);
	drawNumbers(painter);
	pointDoubleClicked(painter);
	pointOnMouseOver(painter);
	pointOnClicked(painter);
	
}

void drawArea::drawPoints(QPainter &painter){

	gPathPoints points(&transform);

	points.addGContainer(*container);
	setStyles(painter,
		Qt::NoBrush,
		POINT_COLOR,
		LINE_STYLE,
		POINT_COLOR);
	painter.drawPath(points);

}

void drawArea::drawCuts(QPainter &painter){

	setStyles(painter,
		Qt::NoBrush,
		LINE_COLOR,
		LINE_STYLE,
		LINE_COLOR);
	painter.drawPath(cuts);

}

void drawArea::drawMoves(QPainter &painter){

	setStyles(painter,
		Qt::NoBrush,
		MOVE_COLOR,
		LINE_STYLE,
		MOVE_COLOR);
	painter.drawPath(moves);

}

const controlPoints &drawArea::getControl(){

	return control;

}

void drawArea::focus(gObject *object, bool flag){
	
	float rect[4];
	int x, y;
	float m;

	once = false;
	transform.reset();
	setFirstTransform();
	if(!object){
		if(control.isDoubleClicked() && flag)
			control.getDoubleClickedGObject()->getRectangle(rect);
		else
			container->getRectangle(rect);
	}
	else
		object->getRectangle(rect);
	rect[0] -= FOCUS_BORDER;
	rect[1] += FOCUS_BORDER;
	rect[2] += FOCUS_BORDER;
	rect[3] -= FOCUS_BORDER;
	x = rect[2] - rect[0];
	y = rect[1] - rect[3];
	if(x - width() > y - height())
		m = width() / (float) x;
	else
		m = height() / (float) y;
	transform.scale(m, m);
	transform.translate(-rect[0], -rect[3]);
	emit scaleChanged();
	update();
	
}

void drawArea::mark(gObject *object){

	if(object){
		control.doubleClicked(object);
		emit gObjectDoubleClicked(object);
		update();
	}

}

void drawArea::middle(){

	control.setMiddle();
	emit gTypeGoClicked(control.getClickedGTypeGo(), control.getClickedPoint());
	update();

}

void drawArea::setDistanceMode(){

	distance == false ? distance = true : distance = false;
	update();

} 

void drawArea::setNumerate(){

	numerate == false ? numerate = true : numerate = false;
	update();

}

void drawArea::setScale(QPointF &point){

	transform.scale(point.x(), point.y());
	emit scaleChanged();
	update();

}

void drawArea::logicTranslate(QPointF &point){

	gObject *object = control.getDoubleClickedGObject();
	
	logicTransform.translation(point.x(), point.y());
	if(object){
		int i = container->getIndexByObject(object);
		
		if(i > -1)
			logicTransform.apply(*buffContainer[i]);
	}
	else
		logicTransform.apply(buffContainer);
	logicTransform.clear();
	updateBuffContainer();
	update();

}

void drawArea::logicRotate(QPointF &point, float angle){

	gObject *object = control.getDoubleClickedGObject();

	logicTransform.rotation(angle, point.x(), point.y());
	if(object){
		int i = container->getIndexByObject(object);
		
		if(i > -1)
			logicTransform.apply(*buffContainer[i]);
	}
	else
		logicTransform.apply(buffContainer);
	logicTransform.clear();
	updateBuffContainer();
	update();

}

bool drawArea::logicMorph(float distance, morphTransformation::morphType type, bool next, bool common){

	gObject *object = control.getDoubleClickedGObject();
	
        if(object && control.getClickedGTypeGo() && object == control.getClickedGObject()){
		int i = container->getIndexByObject(object);
		int j = object->getIndexByObject(control.getClickedGTypeGo());
		gTypeGo *go;
		
		object = buffContainer[i];
		go = dynamic_cast<gTypeGo *>(object->at(j));
		morphTransform.setType(type);
		morphTransform.setDistance(distance);
		morphTransform.setNext(next);
		morphTransform.setCommonPoint(common);
		
		try{
			morphTransform.apply(object, go);
		}
		catch(morphTransformation::morphError &e){
			algorithmProblem problem(this, e.what());
			problem.exec();
		}
		updateBuffContainer();
		update();
		return true;
	}
	else
		return false;

}

void drawArea::drawHighlight(QPainter &painter){

    gObject *object;

    object = control.getDoubleClickedGObject();
    if(object){
        if(object != previousObject){
            float rect[4];

            object->getRectangle(rect);
            clickedRect.setCoords(rect[0] - FOCUS_BORDER,
                                  rect[1] + FOCUS_BORDER,
                                  rect[2] + FOCUS_BORDER,
                                  rect[3] - FOCUS_BORDER);
            previousObject = object;
        }
        setStyles(painter,
                Qt::Dense7Pattern,
                Qt::darkYellow,
                Qt::NoPen,
                LINE_COLOR);
        painter.drawEllipse(transform.mapRect(clickedRect));
    }

}

void drawArea::logicTool(float tool, float z, bool flag, bool common){

	gObject *object = control.getDoubleClickedGObject();

	toolTransform.setParam(tool, z);
	toolTransform.setMoves(flag);
	toolTransform.setCommonPoint(common);
	logicRestore();
	if(object){
		int i = container->getIndexByObject(object);
		
		if(i > -1)
			try{
				toolTransform.apply(*buffContainer[i]);
			}
			catch(toolTransformation::algorithmError &e){
				algorithmProblem problem(this, e.what());
				problem.exec();
			}
	}
	else
		try{
			toolTransform.apply(buffContainer);
		}
		catch(toolTransformation::algorithmError &e){
			algorithmProblem problem(this, e.what());
			problem.exec();
		}
	logicTransform.clear();
	updateBuffContainer();
	update();

}

void drawArea::logicFlush(){

	*container = buffContainer;
	setChanged(true);
	control.update();
	updateContainer();
	update();

}

void drawArea::logicRestore(){

	buffContainer = *container;
	updateBuffContainer();
	update();

}

void drawArea::getScale(QPointF &point) const{

	point.setX(transform.m11());
	point.setY(-transform.m22());

}

void drawArea::setChanged(bool boolean){

	changed = boolean;
	emit changedSignal(this);

}

bool drawArea::getChanged(){

	return changed;

}

gObjectContainer *drawArea::getBuffContainer(){

	return &buffContainer;

}
